{-# LANGUAGE DeriveDataTypeable, GeneralizedNewtypeDeriving #-}

-- | A wrapper of @NonEmpty@ that has a zip-like @Applicative@ instance.
module Data.List.ZipNonEmpty
  (
    ZipNonEmpty
    -- * Accessors
  , ne
  , zipNe
    -- * Combinators
  , usingNe
  , usingZne
  ) where

import Data.List
import Data.Semigroup
import Data.Foldable
import Data.Typeable (Typeable)
import Data.Data (Data)
import Data.List.NonEmpty
import Control.Applicative
import Control.Comonad
import Control.Functor.Pointed
import Control.Functor.Zip

-- | A wrapper of @NonEmpty@ that has a zip-like @Applicative@ instance.
newtype ZipNonEmpty a = Z {
  -- | Unwraps a zip-like non-empty list.
  ne :: NonEmpty a
} deriving ( Eq
           , Ord
           , Typeable
           , Data
           , Functor
           , Pointed
           , Copointed
           , Zip
           , Comonad
           , Semigroup)

-- | Wraps a non-empty list.
zipNe :: NonEmpty a
         -> ZipNonEmpty a
zipNe = Z

-- | Runs a function for non-empty lists on zip-like non-empty lists.
usingNe :: (NonEmpty a -> NonEmpty b)
           -> ZipNonEmpty a
           -> ZipNonEmpty b
usingNe = (zipNe .) . (. ne)

-- | Runs a function for zip-like non-empty lists on non-empty lists.
usingZne :: (ZipNonEmpty a -> ZipNonEmpty b)
            -> NonEmpty a
            -> NonEmpty b
usingZne = (ne .) . (. zipNe)

instance (Show a) => Show (ZipNonEmpty a) where
  show = show . ne

instance Applicative ZipNonEmpty where
  pure = zipNe . unsafeToNonEmpty . repeat
  f <*> a = let z = toList . ne
            in zipNe . unsafeToNonEmpty $ zipWith id (z f) (z a)
